﻿///<copyright>
/// Copyright © 2009 Stephen Styrchak. All Rights Reserved.
///</copyright>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Ferpect.ComponentModel.Design;
using Ferpect.ComponentModel.Sprites;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Ferpect.ComponentModel.Menus
{
    [DefaultProperty("Text")]
    [DefaultEvent("ItemActivated")]
    [Description("Draws a list from which the user can select items.")]
    public class MenuSprite : TextSprite, IGameComponent, IUpdatableChildComponent
    {
        private enum MenuState
        {
            Idle,
            IndexChanging,
            ItemActivating,
            Scrolling,
        }

        private int selectedIndex;
        private int nextSelectedIndex;
        private Color highlightColor;
        private IScreenManager screenManager;
        private Vector2 size;
        private Vector2 clientSize;
        private int firstVisibleItemIndex;
        private int firstItemToDraw;
        private int numItemsToDraw;
        private byte firstVisibleAlpha;
        private byte lastVisibleAlpha;
        private int scrollDirection;
        private int itemsToScroll;
        private AmbientProperties ambientProperties;
        private Rectangle foregroundRect;
        private Rectangle backgroundRect;
        private Vector2 foregroundOrigin;
        private Vector2 backgroundOrigin;
        private Color foreColor;

        private MenuInputHandler inputHandler;
        private Queue<EventHandler<InputEventArgs>> inputQueue;

        private float stateChangingElapsedSeconds;
        private float stateChangeTimeInSeconds;
        private float stateChangeScalar;
        private int maxVisibleItems;

        private bool enabled;
        private int updateOrder;
        private bool autoEnable;
        private IUpdatableParentComponent parent;
        private IScreen parentScreen;
        private MenuState menuState;
        private Vector2[] originOffset;

        [Category("Behavior")]
        public event EventHandler<EventArgs> SelectedIndexChanging;
        [Category("Behavior")]
        public event EventHandler<EventArgs> SelectedIndexChanged;
        [Category("Behavior")]
        public event EventHandler<EventArgs> ItemActivating;
        [Category("Behavior")]
        public event EventHandler<EventArgs> ItemActivated;
        [Category("Behavior")]
        public event EventHandler<EventArgs> BeforeScrolling;
        [Category("Behavior")]
        public event EventHandler<EventArgs> AfterScrolling;

        public MenuSprite()
        {
            this.Items = new CollectionWithEvents<string>();
            this.Items.ItemAdded += this.OnItemAdded;
            this.Items.ItemRemoved += this.OnItemRemoved;
            this.Color = Color.White;
            this.highlightColor = Color.Yellow;
            this.selectedIndex = -1;
            this.autoEnable = true;
            this.MaxVisibleItems = 8;
            this.BackColor = Color.Multiply(Color.Black, 40f/255f);
            this.ForeColor = Color.Multiply(Color.White, 100f/255f);

            this.inputQueue = new Queue<EventHandler<InputEventArgs>>();
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public AmbientProperties AmbientProperties { get { return this.ambientProperties; } }

        /// <summary>
        /// When true, this component will match its Enabled property value
        /// to that of its parent's Enabled property.
        /// </summary>
        [Category("Behavior")]
        [DefaultValue(true)]
        public bool AutoEnable
        {
            get { return this.autoEnable; }
            set
            {
                if (this.autoEnable != value)
                {
                    this.autoEnable = value;
                    // Don't sync to the parent or handle the EnableChanged event in design mode.
                    if (this.parent != null && !this.DesignMode)
                    {
                        this.Enabled = this.parent.Enabled;
                        if (this.autoEnable)
                        {
                            this.parent.EnabledChanged += this.OnParentEnabledChanged;
                        }
                        else
                        {
                            this.parent.EnabledChanged -= this.OnParentEnabledChanged;
                        }
                    }
                }
            }
        }

        [Category("Appearance")]
        [Editor(UITypeEditors.ColorEditor, UITypeEditors.UITypeEditor)]
        [DefaultValue(typeof(Color), "0,0,0,40")]
        public Color BackColor { get; set; }

        [Category("Appearance")]
        [Editor(UITypeEditors.ColorEditor, UITypeEditors.UITypeEditor)]
        [DefaultValue(typeof(Color), "255,255,255,100")]
        public Color ForeColor { get; set; }

        protected virtual Vector2 ClientPosition
        {
            get
            {
                return this.Position;
            }
        }

        [Category("Appearance")]
        [Description("Specifies the color of menu item text when the menu item has focus.")]
        [Editor(UITypeEditors.ColorEditor, UITypeEditors.UITypeEditor)]
        [DefaultValue(typeof(Color), "255,255,0,255")]
        public Color HighlightColor
        {
            get { return this.highlightColor; }
            set { this.highlightColor = value; }
        }

        public MenuInputHandler InputHandler
        {
            get { return this.inputHandler; }
            set
            {
                if (this.inputHandler != null)
                {
                    this.inputHandler.Down -= this.OnInputDown;
                    this.inputHandler.Up -= this.OnInputUp;
                    this.inputHandler.Accept -= this.OnInputAccept;
                    this.inputHandler.Cancel -= this.OnInputCancel;
                    this.inputHandler.Right -= this.OnInputRight;
                    this.inputHandler.Left -= this.OnInputLeft;
                    this.inputHandler.PageDown -= this.OnInputPageDown;
                    this.inputHandler.PageUp -= this.OnInputPageUp;
                }
                if (value != null)
                {
                    this.inputHandler = value;
                    this.inputHandler.Down += this.OnInputDown;
                    this.inputHandler.Up += this.OnInputUp;
                    this.inputHandler.Accept += this.OnInputAccept;
                    this.inputHandler.Cancel += this.OnInputCancel;
                    this.inputHandler.Right += this.OnInputRight;
                    this.inputHandler.Left += this.OnInputLeft;
                    this.inputHandler.PageDown += this.OnInputPageDown;
                    this.inputHandler.PageUp += this.OnInputPageUp;
                }
            }
        }

        [Category("Data")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Editor(UITypeEditors.ListControlStringCollectionEditor, UITypeEditors.UITypeEditor)]
        public CollectionWithEvents<string> Items { get; private set; }

        [Category("Appearance")]
        [DefaultValue(8)]
        public int MaxVisibleItems
        {
            get { return this.maxVisibleItems; }
            set
            {
                if (value < 1)
                {
                    throw new ArgumentOutOfRangeException("value", "MaxVisibleItems must be greater than zero.");
                }
                if (value > this.maxVisibleItems)
                {
                    this.originOffset = new Vector2[value + 1];
                }
                if (value != this.maxVisibleItems)
                {
                    this.maxVisibleItems = value;
                    this.UpdateSize();
                }
            }
        }

        /// <summary>
        /// Allows derived classes to suspend input processing. This can be useful
        /// when animating transitions between menu items, to prevent focus changes
        /// until the animation is complete.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        protected bool SuspendInput { get; set; }

        [Category("Layout")]
        [DefaultValue(TextLayout.Center)]
        public TextLayout TextLayout
        {
            get;
            set;
        }

        protected virtual void OnInputDown(object sender, InputEventArgs args)
        {
            if (this.SuspendInput) return;

            if (this.menuState == MenuState.Idle)
            {
                this.ChangeSelectedIndex(this.SelectedIndex + 1);
            }
            else
            {
                this.inputQueue.Enqueue(this.OnInputDown);
            }
        }

        protected virtual void OnInputUp(object sender, InputEventArgs args)
        {
            if (this.SuspendInput) return;

            if (this.menuState == MenuState.Idle)
            {
                this.ChangeSelectedIndex(this.SelectedIndex - 1);
            }
            else
            {
                this.inputQueue.Enqueue(this.OnInputUp);
            }
        }

        protected virtual void OnInputPageDown(object sender, InputEventArgs args)
        {
            if (this.SuspendInput) return;

            if (this.menuState == MenuState.Idle)
            {
                int lastVisible = this.firstVisibleItemIndex + this.maxVisibleItems - 1;
                if (this.selectedIndex == lastVisible)
                {
                    this.ChangeSelectedIndex(this.SelectedIndex + this.maxVisibleItems - 1);
                }
                else
                {
                    this.ChangeSelectedIndex(lastVisible);
                }
            }
            else
            {
                this.inputQueue.Enqueue(this.OnInputPageDown);
            }
        }

        protected virtual void OnInputPageUp(object sender, InputEventArgs args)
        {
            if (this.SuspendInput) return;

            if (this.menuState == MenuState.Idle)
            {
                if (this.selectedIndex == this.firstVisibleItemIndex)
                {
                    this.ChangeSelectedIndex(this.SelectedIndex - this.maxVisibleItems + 1);
                }
                else
                {
                    this.ChangeSelectedIndex(this.firstVisibleItemIndex);
                }
            }
            else
            {
                this.inputQueue.Enqueue(this.OnInputPageUp);
            }
        }

        protected virtual void OnInputAccept(object sender, InputEventArgs args)
        {
            if (this.SuspendInput) return;

            if (this.menuState == MenuState.Idle)
            {
                this.menuState = MenuState.ItemActivating;
                this.stateChangingElapsedSeconds = 0;
                this.stateChangeTimeInSeconds = 0.1f;
                this.stateChangeScalar = 0;
                if (this.ItemActivating != null)
                {
                    this.ItemActivating(this, EventArgs.Empty);
                }
            }
            else
            {
                this.inputQueue.Enqueue(this.OnInputAccept);
            }
        }

        protected virtual void OnInputCancel(object sender, InputEventArgs args)
        {
            if (this.SuspendInput) return;

        }

        protected virtual void OnInputRight(object sender, InputEventArgs args)
        {
            if (this.SuspendInput) return;

        }

        protected virtual void OnInputLeft(object sender, InputEventArgs args)
        {
            if (this.SuspendInput) return;

        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IScreenManager ScreenManager { get { return this.screenManager; } }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int SelectedIndex
        {
            get
            {
                return this.selectedIndex;
            }
            set
            {
                if (value != this.selectedIndex)
                {
                    this.selectedIndex = value;
                    if (this.SelectedIndexChanged != null)
                    {
                        this.SelectedIndexChanged(this, EventArgs.Empty);
                    }
                }
            }
        }

        /// <summary>
        /// Returns the currently selected value, or null if the SelectedIndex
        /// is outside the range of valid indices.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string Value
        {
            get
            {
                string value = null;
                if (this.selectedIndex >= 0 && this.selectedIndex < this.Items.Count)
                {
                    value = this.Items[this.selectedIndex];
                }
                return value;
            }
        }

        protected int NextSelectedIndex
        {
            get
            {
                return this.nextSelectedIndex;
            }
        }

        /// <summary>
        /// This function initiates the selected index changing animation.
        /// </summary>
        public void ChangeSelectedIndex(int newIndex)
        {
            if (this.Items.Count > 0)
            {
                //this.nextSelectedIndex = (int)MathHelper.Clamp(newIndex, 0, Math.Min(this.MaxVisibleItems, this.Items.Count) - 1);
                this.nextSelectedIndex = (int)MathHelper.Clamp(newIndex, 0, this.Items.Count - 1);

                MenuState newState = MenuState.IndexChanging;
                this.scrollDirection = 0;
                if (this.nextSelectedIndex < this.firstVisibleItemIndex)
                {
                    // Scroll items down to move new selection into view
                    newState = MenuState.Scrolling;
                    this.scrollDirection = 1;
                    this.itemsToScroll = this.firstVisibleItemIndex - this.nextSelectedIndex;
                }
                else if (this.nextSelectedIndex >= (this.firstVisibleItemIndex + this.maxVisibleItems))
                {
                    // Scroll items up to move new selection into view
                    newState = MenuState.Scrolling;
                    this.scrollDirection = -1;
                    this.itemsToScroll = this.nextSelectedIndex - (this.firstVisibleItemIndex + this.maxVisibleItems - 1);
                }

                // Reset the transition variables.
                this.stateChangingElapsedSeconds = 0;
                this.stateChangeTimeInSeconds = 0.1f;
                this.stateChangeScalar = 0;

                MenuState oldMenuState = this.menuState;
                this.menuState = newState;
                try
                {
                    if (this.SelectedIndexChanging != null)
                    {
                        this.SelectedIndexChanging(this, EventArgs.Empty);
                    }
                    if (this.menuState == MenuState.Scrolling && this.BeforeScrolling != null)
                    {
                        this.BeforeScrolling(this, EventArgs.Empty);
                    }
                }
                catch
                {
                    this.nextSelectedIndex = this.selectedIndex;
                    this.menuState = oldMenuState;
                    throw;
                }
            }
        }

        protected virtual void UpdateBackground(GameTime gameTime)
        {
            this.backgroundRect = new Rectangle();

            this.backgroundRect.X = (int)this.Position.X;
            this.backgroundRect.Y = (int)this.Position.Y;
            this.backgroundRect.Width = (int)(this.size.X * this.Scale);
            this.backgroundRect.Height = (int)(this.size.Y * this.Scale);

            Vector2 backgroundScale = new Vector2();
            backgroundScale.X = this.Scale / ((float)this.backgroundRect.Width / (float)this.ambientProperties.BlankTexture.Width);
            backgroundScale.Y = this.Scale / ((float)this.backgroundRect.Height / (float)this.ambientProperties.BlankTexture.Height);

            this.backgroundOrigin = new Vector2();
            this.backgroundOrigin.X = (this.Origin.X) * backgroundScale.X;
            this.backgroundOrigin.Y = (this.Origin.Y) * backgroundScale.Y;
        }

        protected virtual void DrawBackground(SpriteBatchStack spriteBatchStack)
        {
            spriteBatchStack.Current.Draw(this.AmbientProperties.BlankTexture, this.backgroundRect, null, this.BackColor, this.Rotation, this.backgroundOrigin, this.Effects, this.LayerDepth);
        }

        protected virtual void UpdateVisibleItems(GameTime gameTime)
        {
            float scrollOffset = 0f;
            int firstInView = this.firstVisibleItemIndex;
            this.numItemsToDraw = Math.Min(this.Items.Count, this.maxVisibleItems);

            if (this.menuState == MenuState.Scrolling)
            {
                // This is the number of items we've scrolled past so far.
                float itemsScrolled = this.stateChangeScalar * (float)this.itemsToScroll;
                float mantissaScrolled = (itemsScrolled - (float)Math.Floor(itemsScrolled));

                if (this.scrollDirection == 1)
                {
                    // Scrolling items down, a partial item reveals the one before.
                    firstInView = firstInView - (int)Math.Ceiling(itemsScrolled);
                    if (firstInView < 0)
                    {
                        firstInView = 0;
                    }
                    // The one before the visible list is partially in view.
                    scrollOffset = (1f - mantissaScrolled) * (float)this.Font.LineSpacing;
                    byte alpha = this.Color.A;
                    this.firstVisibleAlpha = (byte)(mantissaScrolled * mantissaScrolled * (float)alpha);
                    this.lastVisibleAlpha = (byte)((1f - mantissaScrolled * mantissaScrolled) * (float)alpha);
                }
                else
                {
                    // Scrolling items up, a partial item reveals the next visible item.
                    firstInView = firstInView + (int)Math.Floor(itemsScrolled);
                    if (firstInView + this.maxVisibleItems > this.Items.Count)
                    {
                        firstInView = this.Items.Count - this.maxVisibleItems;
                    }
                    scrollOffset = mantissaScrolled * (float)this.Font.LineSpacing;
                    byte alpha = this.Color.A;
                    this.firstVisibleAlpha = (byte)((1f - mantissaScrolled) * (float)alpha);
                    this.lastVisibleAlpha = (byte)(mantissaScrolled * (float)alpha);
                }

                int visibleItems = Math.Min(this.Items.Count, this.maxVisibleItems + 1);
                if (this.firstItemToDraw + visibleItems <= this.Items.Count)
                {
                    this.numItemsToDraw = visibleItems;
                }
            }

            this.firstItemToDraw = firstInView;
            Vector2 offset = new Vector2();
            offset.Y = -scrollOffset;
            for (int i = 0; i < this.numItemsToDraw; ++i)
            {
                string item = this.Items[i + this.firstItemToDraw];
                Vector2 itemSize = this.Font.MeasureString(item);

                if (this.TextLayout == TextLayout.Center)
                {
                    offset.X = (int)((this.clientSize.X - itemSize.X) / 2);
                }
                else if (this.TextLayout == TextLayout.Right)
                {
                    offset.X = (this.clientSize.X - itemSize.X);
                }
                this.originOffset[i] = offset;

                offset.Y += itemSize.Y;
            }
        }

        private void DrawVisibleItems(SpriteBatchStack spriteBatchStack)
        {
            if (this.numItemsToDraw > 0)
            {
                for (int i = 0; i < this.numItemsToDraw; ++i)
                {
                    byte alpha = this.Color.A;
                    if (this.menuState == MenuState.Scrolling)
                    {
                        if (i == 0)
                        {
                            alpha = this.firstVisibleAlpha;
                        }
                        else if (i == this.numItemsToDraw - 1)
                        {
                            alpha = this.lastVisibleAlpha;
                        }
                    }
                    this.DrawItemAtIndex(spriteBatchStack, this.firstItemToDraw + i, this.originOffset[i], alpha);
                }
            }
            else
            {
                spriteBatchStack.Current.DrawString(this.Font, this.Text, this.Position, this.Color, this.Rotation, this.Origin, this.Scale, this.Effects, this.LayerDepth);
            }
        }

        protected virtual void DrawItemAtIndex(SpriteBatchStack spriteBatchStack, int index, Vector2 originOffset, Byte alpha)
        {
            Color color = Color.Multiply(this.Color, ((float)alpha)/255f);
            if (index == this.selectedIndex && this.menuState != MenuState.IndexChanging)
            {
                color = this.highlightColor;
            }

            string item = this.Items[index];

            spriteBatchStack.Current.DrawString(this.Font, item, this.Position, color, this.Rotation, this.Origin - originOffset, this.Scale, this.Effects, 0);
        }

        protected virtual void UpdateForeground(GameTime gameTime)
        {
            this.foregroundRect = new Rectangle();
            if (-1 < this.selectedIndex && this.selectedIndex < this.Items.Count)
            {
                this.foregroundRect.X = (int)this.Position.X;
                this.foregroundRect.Y = (int)this.Position.Y;
                this.foregroundRect.Width = (int)((this.size.X + 4) * this.Scale + 0.5f);
                this.foregroundRect.Height = (int)((this.Font.LineSpacing + 4) * this.Scale + 0.5f);

                Vector2 foregroundScale = new Vector2();
                foregroundScale.X = this.Scale / ((float)this.foregroundRect.Width / (float)this.ambientProperties.BlankTexture.Width);
                foregroundScale.Y = this.Scale / ((float)this.foregroundRect.Height / (float)this.ambientProperties.BlankTexture.Height);

                float yOriginOffset = GetSelectedItemOriginOffset();
                this.foregroundOrigin.X = (this.Origin.X + 2f) * foregroundScale.X;
                this.foregroundOrigin.Y = (this.Origin.Y - yOriginOffset) * foregroundScale.Y;
            }
            if (this.menuState == MenuState.ItemActivating)
            {
                this.foreColor = Color.Lerp(this.ForeColor, Color.White, 0.5f - Math.Abs(0.5f - this.stateChangeScalar));
            }
            else
            {
                this.foreColor = this.ForeColor;
            }
        }

        protected virtual void DrawForeground(SpriteBatchStack spriteBatchStack)
        {
            spriteBatchStack.Current.Draw(this.AmbientProperties.BlankTexture, this.foregroundRect, null, this.foreColor, this.Rotation, this.foregroundOrigin, this.Effects, this.LayerDepth);
        }

        private float GetSelectedItemOriginOffset()
        {
            if (this.menuState == MenuState.IndexChanging)
            {
                float first = this.originOffset[this.selectedIndex - this.firstVisibleItemIndex].Y;
                float second = this.originOffset[this.nextSelectedIndex - this.firstVisibleItemIndex].Y;

                return MathHelper.Lerp(first, second, this.stateChangeScalar);
            }
            else if (this.menuState == MenuState.Scrolling)
            {
                if (this.scrollDirection == 1)
                {
                    return 0f;
                }
                else
                {
                    return (this.maxVisibleItems - 1) * this.Font.LineSpacing;
                }
            }
            return this.originOffset[this.selectedIndex - this.firstVisibleItemIndex].Y;
        }

        #region Sprite overrides

        public override void Draw(SpriteBatchStack spriteBatchStack)
        {
            if (this.DesignMode)
            {
                this.Update(new GameTime());
            }
            this.DrawBackground(spriteBatchStack);
            this.DrawVisibleItems(spriteBatchStack);
            this.DrawForeground(spriteBatchStack);
        }

        public override void LoadContent(Microsoft.Xna.Framework.Content.ContentManager contentManager)
        {
            this.ambientProperties = (AmbientProperties)this.GetService(typeof(AmbientProperties));
            this.screenManager = (IScreenManager)this.GetService(typeof(IScreenManager));
            base.LoadContent(contentManager);
            this.UpdateSize();
            this.ResetRelativeOrigin();
        }

        public override Vector2 GetSize()
        {
            return this.size;
        }

        #endregion

        protected virtual Vector2 GetClientSize()
        {
            return this.clientSize;
        }

        /// <summary>
        /// Handler for when the user has chosen a menu entry.
        /// </summary>
        protected virtual void OnSelectEntry()
        {
            //this.Items[this.selectedEntry].OnSelected();
        }

        /// <summary>
        /// Handler for when the user has cancelled the menu.
        /// </summary>
        protected virtual void OnCancel()
        {
            this.ScreenManager.Return();
        }

        protected virtual void UpdateSize()
        {
            if (this.Font != null)
            {
                float maxWidth = 0;
                float height = 0;
                int visibleItemCount = (this.MaxVisibleItems < 0) ? this.Items.Count : Math.Min(this.Items.Count, this.MaxVisibleItems);
                if (visibleItemCount > 0)
                {
                    for (int i = 0; i < visibleItemCount; ++i)
                    {
                        Vector2 itemSize = this.Font.MeasureString(this.Items[this.firstVisibleItemIndex + i]);
                        maxWidth = MathHelper.Max(maxWidth, itemSize.X);
                        height += itemSize.Y;
                    }
                    this.size.X = maxWidth;
                    this.size.Y = height;
                }
                else if (this.Text != null)
                {
                    this.size = this.Font.MeasureString(this.Text);
                }
            }
            else
            {
                this.size = Vector2.Zero;
            }

            this.clientSize = this.size;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.Items != null)
                {
                    this.Items.ItemAdded -= this.OnItemAdded;
                    this.Items.ItemRemoved -= this.OnItemRemoved;
                }
            }
            base.Dispose(disposing);
        }

        #region IGameComponent Members

        public void Initialize()
        {
        }

        #endregion

        #region IUpdatableChildComponent Members

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IUpdatableParentComponent Parent
        {
            get { return this.parent; }
            set
            {
                if (!Object.ReferenceEquals(this.parent, value))
                {
                    if (this.parent != null)
                    {
                        if (this.autoEnable)
                        {
                            this.parent.EnabledChanged -= this.OnParentEnabledChanged;
                            this.parent.UpdateOrderChanged -= this.OnParentUpdateOrderChanged;
                        }
                        this.parent.Components.Remove(this);
                    }
                    if (this.parentScreen != null)
                    {
                        this.parentScreen.FocusChanged -= this.OnParentFocusChanged;
                    }
                }
                this.parent = value;
                if (value != null)
                {
                    if (this.autoEnable)
                    {
                        this.parent.EnabledChanged += this.OnParentEnabledChanged;
                        this.parent.UpdateOrderChanged += this.OnParentUpdateOrderChanged;
                        this.Enabled = this.parent.Enabled;
                        this.UpdateOrder = this.parent.UpdateOrder + 1;
                    }
                    if (!this.parent.Components.Contains(this))
                    {
                        this.parent.Components.Add(this);
                    }
                    this.parentScreen = this.parent as IScreen;
                    if (this.parentScreen != null)
                    {
                        this.parentScreen.FocusChanged += this.OnParentFocusChanged;
                        this.SuspendInput = !this.parentScreen.HasFocus;
                    }
                }
                else
                {
                    if (this.autoEnable)
                    {
                        this.Enabled = false;
                    }
                    this.SuspendInput = true;
                }
            }
        }

        #endregion

        #region IUpdateable Members

        [Category("Behavior")]
        [Description("Indicates whether the component should be updated. If false, the Update method will not be called.")]
        [DefaultValue(true)]
        public bool Enabled
        {
            get
            {
                return this.enabled;
            }
            set
            {
                if (this.enabled != value)
                {
                    this.enabled = value;
                    this.OnEnabledChanged(this, EventArgs.Empty);
                }
            }
        }

        [Category("Property Changed")]
        public event EventHandler<EventArgs> EnabledChanged;

        public void Update(GameTime gameTime)
        {
            this.UpdateFromInputQueue();
            this.UpdateTransition(gameTime);
            this.UpdateBackground(gameTime);
            this.UpdateVisibleItems(gameTime);
            this.UpdateForeground(gameTime);
        }

        private void UpdateFromInputQueue()
        {
            if (this.SuspendInput)
            {
                this.inputQueue.Clear();
            }
            if (this.menuState == MenuState.Idle && this.inputQueue.Count > 0)
            {
                var inputEvent = this.inputQueue.Dequeue();
                inputEvent(null, null);
            }
        }

        private void UpdateTransition(GameTime gameTime)
        {
            if (this.menuState != MenuState.Idle)
            {
                this.stateChangingElapsedSeconds += (float)gameTime.ElapsedGameTime.TotalSeconds;
                this.stateChangeScalar = MathHelper.Clamp(this.stateChangingElapsedSeconds / this.stateChangeTimeInSeconds, 0, 1);

                if (this.stateChangeScalar >= 1f - Single.Epsilon)
                {
                    MenuState fromState = this.menuState;
                    this.menuState = MenuState.Idle;

                    switch (fromState)
                    {
                        case MenuState.IndexChanging:
                            this.selectedIndex = this.nextSelectedIndex;
                            this.OnSelectedIndexChanged(gameTime);
                            if (this.SelectedIndexChanged != null)
                            {
                                this.SelectedIndexChanged(this, EventArgs.Empty);
                            }
                            break;
                        case MenuState.ItemActivating:
                            this.OnItemActivated(gameTime);
                            if (this.ItemActivated != null)
                            {
                                this.ItemActivated(this, EventArgs.Empty);
                            }
                            break;
                        case MenuState.Scrolling:
                            if (this.nextSelectedIndex < this.firstVisibleItemIndex)
                            {
                                this.firstVisibleItemIndex = this.nextSelectedIndex;
                            }
                            else
                            {
                                this.firstVisibleItemIndex = this.nextSelectedIndex - this.maxVisibleItems + 1;
                            }
                            this.OnAfterScrolling(gameTime);
                            if (this.AfterScrolling != null)
                            {
                                this.AfterScrolling(this, EventArgs.Empty);
                            }
                            // Repeat IndexChanging
                            this.selectedIndex = this.nextSelectedIndex;
                            this.OnSelectedIndexChanged(gameTime);
                            if (this.SelectedIndexChanged != null)
                            {
                                this.SelectedIndexChanged(this, EventArgs.Empty);
                            }
                            break;
                        default:
                            Debug.Assert(false, "MenuSprite transition completed from an unknown state.");
                            break;
                    }
                }
            }
        }

        protected virtual void OnSelectedIndexChanged(GameTime gameTime)
        {
        }

        protected virtual void OnItemActivated(GameTime gameTime)
        {
        }

        protected virtual void OnAfterScrolling(GameTime gameTime)
        {
        }

        [Category("Behavior")]
        [Description("Indicates when the game component should be updated relative to other game components. Lower values are updated first.")]
        [DefaultValue(0)]
        public int UpdateOrder
        {
            get
            {
                return this.updateOrder;
            }
            set
            {
                if (this.updateOrder != value)
                {
                    this.updateOrder = value;
                    this.OnUpdateOrderChanged(this, EventArgs.Empty);
                }
            }
        }

        [Category("Property Changed")]
        public event EventHandler<EventArgs> UpdateOrderChanged;

        #endregion

        protected virtual void OnParentEnabledChanged(object sender, EventArgs args)
        {
            Debug.Assert(this.autoEnable, "We should only be handling IUpdatableParentComponent.EnabledChanged if AutoEnable is true.");
            this.Enabled = this.parent.Enabled;
        }

        protected virtual void OnParentUpdateOrderChanged(object sender, EventArgs args)
        {
            Debug.Assert(this.autoEnable, "We should only be handling IUpdatableParentComponent.UpdateOrderChanged if AutoEnable is true.");
            this.UpdateOrder = this.parent.UpdateOrder + 1;
        }

        protected virtual void OnParentFocusChanged(object sender, EventArgs args)
        {
            Debug.Assert(this.parentScreen != null, "We should only be handling IScreen.FocusChanged if the parent component implements IScreen.");
            this.SuspendInput = !this.parentScreen.HasFocus;
        }

        protected virtual void OnEnabledChanged(object sender, EventArgs args)
        {
            if (this.EnabledChanged != null)
            {
                this.EnabledChanged(this, args);
            }
        }

        protected virtual void OnUpdateOrderChanged(object sender, EventArgs args)
        {
            if (this.UpdateOrderChanged != null)
            {
                this.UpdateOrderChanged(this, args);
            }
        }

        private void OnItemAdded(object sender, EventArgs args)
        {
            if (this.selectedIndex < 0)
            {
                this.selectedIndex = 0;
            }
            this.UpdateSize();
        }

        private void OnItemRemoved(object sender, EventArgs args)
        {
            if (this.selectedIndex >= this.Items.Count)
            {
                this.selectedIndex -= 1;
            }
            this.UpdateSize();
        }

    }
}
